package com.abel.bigwater.data.jnrq

import com.abel.bigwater.PacketHelper.hexString
import com.abel.bigwater.PacketHelper.rawArray
import com.abel.bigwater.data.BcdString
import com.abel.bigwater.data.InvalidPacketException
import com.abel.bigwater.data.NotEnoughDataException
import com.abel.bigwater.data.UnknownPacketException
import io.netty.buffer.ByteBuf
import org.slf4j.LoggerFactory

enum class PacketType private constructor(_code: Short?) {
    DownloadAddress(0x44),
    AnswerDownloadAddr(0x45),
    UpdateIp(0x43),
    UploadHistoryEmpty(0x46),
    AdjustTime(0x42);

    val code: Short? = _code

    companion object {
        fun findByCode(_code: Short): PacketType? {
            return PacketType.values().find { it.code == _code }
        }
    }
}

class JnrqPacket {

    var packetType: PacketType? = null

    /**
     * 设备号 A 设备地址 4 个字节
     * 多字节数据传送顺序均先传低字节， 再传高字节， 本协议文件中均符合此规则
     */
    var devAddrInt: Int? = null

    /** 包长 L 包长 len 2 个字节 采用 HEX 码
     * 识别数据帧长度 L； 从帧起始 68H 到包围 0DH 0AH 中所有字节的总和（包
    括帧起始与包尾）。
     */
    var len: Int? = null

    /** 数据域 D 数据域 Len-13 个字节*/
    var data: JnrqData? = null

    /**
     * 原始文本
     */
    var rawText: String? = null

    /**
     * will be base64 in json.
     */
    var rawBinary: ByteArray? = null

    /**
     * 间隔字节数
     */
    var skipCnt: Int = 0

    /** 校验符 校验字 CRC 1 字节 累加和*/
    var crc: Byte? = null

    /**
     * 填充字节流
     */
    @Throws(IllegalArgumentException::class, NullPointerException::class)
    fun fillBuffer(buf: ByteBuf) {
        // 计算长度
        computeLen()

        val tmp = buf.alloc().buffer().also {
            it.writeBytes(packetHead)
            it.writeByte(packetType!!.code!!.toInt())
            it.writeIntLE(devAddrInt!!)
            it.writeShortLE(10 + data?.dataLen!! + 3)
            data!!.fillBuffer(it)

            // 帧校验码 CRC； 从帧起始符开始到校验和之前的所有各字节进行算术累
            // 加，不计超出 FFH 的溢出值结束字符；
            crc = BcdString.algorithmSum(it.duplicate(), it.readableBytes())
            it.writeByte(crc!!.toInt())

            it.writeBytes(packetTail)
        }

        // write entire
        buf.writeBytes(tmp)
    }

    /**
     * 计算长度
     */
    fun computeLen() {
        len = data!!.computeDataLen() + 13
    }

    companion object {
        const val MIN_LEN = 3 + 1 + 4 + 2 + 1 + 2

        val packetHead = byteArrayOf(0x68, 0x11, 0x55)

        val packetTail = byteArrayOf(0x0D, 0x0A)

        internal fun findHead(buf: ByteBuf): Int? {
            // find head position
            var cnt = 0
            buf.duplicate().apply {
                while (!packetHead.contentEquals(duplicate().rawArray(3))) {
                    skipBytes(1)
                    ++cnt

                    // to avoid excessive consuming
                    if (cnt > MIN_LEN) {
                        return null
                    }
                }
            }

            if (cnt > 0) {
                lgr.warn("some redundant bytes {}: {}", cnt, buf.duplicate().hexString(cnt))
            }

            return cnt
        }

        /**
         * split whole message from specified buffer.
         * The {@link HgdaoBase.station} should be filled after successful splitting.
         * The respective data in specified will be consumed.
         * The implementations should never throw any exception!!
         * @param buf the buffer contains data
         * @return the new buffer contains complete packet
         * @see JnrqPacket
         */
        fun splitPacket(buf: ByteBuf): JnrqPacket? {
            val p = JnrqPacket().apply {
                skipCnt = findHead(buf) ?: return null
            }

            // parse from shallow-copy of buffer.
            buf.duplicate().skipBytes(p.skipCnt).apply {
                if (readableBytes() < MIN_LEN) {
                    return null
                }

                // consume head(hex): 68 11 55
                skipBytes(3)

                p.packetType = PacketType.findByCode(readUnsignedByte())
                p.devAddrInt = readIntLE()
                p.len = readShortLE().toInt()
                if (readableBytes() < p.len!! - 13 + 1 + 2) {
                    return null
                }
            }

            // fetch raw-array but do not disturb original buffer.
            return p.apply {
                rawBinary = buf.duplicate().rawArray(skipCnt + len!!)
            }
        }

        /**
         * 解析字节流
         */
        @Throws(NotEnoughDataException::class, UnknownPacketException::class, InvalidPacketException::class)
        fun parseBuffer(buf: ByteBuf, _skipCnt: Int? = null): JnrqPacket {
            val p = JnrqPacket().apply {
                skipCnt = _skipCnt ?: findHead(buf)
                        ?: throw UnknownPacketException("包头不识别: ${buf.duplicate().hexString(MIN_LEN)}")
            }

            buf.duplicate().apply {
                if (skipBytes(p.skipCnt).readableBytes() < MIN_LEN) {
                    throw NotEnoughDataException("字节数不足: ${readableBytes()}")
                }

                p.rawText = duplicate().hexString()

                // head hex: 68 11 55
                skipBytes(3)

                val code = readUnsignedByte()
                p.packetType = PacketType.findByCode(code)
                        ?: throw UnknownPacketException("无法识别的功能码: ${code.toString(16)}")

                p.devAddrInt = readIntLE()
                p.len = readUnsignedShortLE()
                if (readableBytes() < p.len!! - 13 + 1 + 2) {
                    val msg = "字节数不足: ${readableBytes()} < ${p.len!! - 13 + 1 + 2}"
                    lgr.info(msg)
                    throw NotEnoughDataException(msg)
                }

                // 帧结束符
                val tailBuf = duplicate().skipBytes(p.len!! - 13).readBytes(1 + 2)
                p.crc = tailBuf.readByte()
                val bsTail = BcdString.create(tailBuf, 2)
                if (!packetTail.contentEquals(bsTail.hexArray!!)) {
                    throw InvalidPacketException("不正确的结束符: ${bsTail.bcdString}")
                }

                // crc校验
                val crcRaw = BcdString.algorithmSum(buf.skipBytes(p.skipCnt!!).duplicate(), p.len!! - 1 - 2)
                if (crcRaw != p.crc) {
                    lgr.error("不正确的校验码: ${crcRaw} != ${p.crc}")
                }

                when (p.packetType) {
                    // 4、 上传历史包：上行报文
                    PacketType.UploadHistoryEmpty -> {
                        // 5、上传空数据包（握手包） 上行报文
                        if (p.len == 25) {
                            p.data = UploadEmptyData(p).also {
                                it.parseBuffer(readBytes(it.dataLen!!))
                            }
                        } else {
                            p.data = UploadHistoryData(p).also {
                                it.dataLen = p.len!! - 13
                                it.parseBuffer(readBytes(it.dataLen!!))
                            }
                        }
                    }

                    // 2、应答下载地址表，上行报文（集中器→服务器）
                    PacketType.AnswerDownloadAddr -> {
                        p.data = JnrqDownloadAddrReply().also {
                            it.parseBuffer(readBytes(it.dataLen!!))
                        }
                    }

                    else -> {
                        lgr.error("ignore packet type {}: {}", p.packetType, readBytes(p.len!! - 13).hexString())
                    }
                }
            }

            // skip bytes for next message
            buf.skipBytes(p.len!!)

            return p
        }


        private val lgr = LoggerFactory.getLogger(JnrqPacket::class.java)

        const val BIG_METER_STR_FLAG = "82"

        const val BIG_METER_FLAG: Byte = 0x82.toByte()
    }
}